/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2009 - 2012 Luca Mingardi.
 *
 * This file is part of jeeObserver.
 *
 * JeeObserver is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * JeeObserver is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package jeeobserver;

import java.io.Serializable;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.ThreadMXBean;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import jeeobserver.utilities.TimeUtilities;

public class JvmObserver extends Observer implements Serializable {

	 private static final long serialVersionUID = 40L;

	 private Date date;

	 private transient final Map<Long, Long> threadsMap = new HashMap<Long, Long>();

	 private transient final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

	 private transient static MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();

	 private transient static ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();

	 private transient static List<GarbageCollectorMXBean> garbageCollectorMXBeans = ManagementFactory.getGarbageCollectorMXBeans();

	 private transient long startTimeNanos;

	 private transient long stopTimeNanos;

	 private long timeNanos;

	 private long cpuNanos;

	 private double cpuUsagePercentage;

	 private int processors;

	 private int threads;

	 private long heapMemoryUsedBytes;

	 private long heapMemoryCommittedBytes;

	 private long heapMemoryMaxBytes;

	 private long nonHeapMemoryUsedBytes;

	 private long nonHeapMemoryCommittedBytes;

	 private long nonHeapMemoryMaxBytes;

	 private long loadedClasses;

	 private long garbageCollections;

	 private long garbageCollectionTimeNanos;

	 JvmObserver() {
			super();
	 }

	 //Test
	 JvmObserver(Date date, String context, long timeNanos, long cpuNanos, int processors, int threads, long heapMemoryUsedBytes, long heapMemoryCommittedBytes, long heapMemoryMaxBytes, long nonHeapMemoryUsedBytes, long nonHeapMemoryCommittedBytes, long nonHeapMemoryMaxBytes) {
			super(context);
			this.date = date;
			this.timeNanos = timeNanos;
			this.cpuNanos = cpuNanos;
			this.processors = processors;

			this.cpuUsagePercentage = (new Double(this.cpuNanos) / new Double(this.timeNanos * this.processors));
			this.threads = threads;
			this.heapMemoryUsedBytes = heapMemoryUsedBytes;
			this.heapMemoryCommittedBytes = heapMemoryCommittedBytes;
			this.heapMemoryMaxBytes = heapMemoryMaxBytes;
			this.nonHeapMemoryUsedBytes = nonHeapMemoryUsedBytes;
			this.nonHeapMemoryCommittedBytes = nonHeapMemoryCommittedBytes;
			this.nonHeapMemoryMaxBytes = nonHeapMemoryMaxBytes;
	 }

	 void start() {

			if (JeeObserverContext.getInstance() != null) {


				 // CPU
				 this.setRunning(true);

				 // The order of these two calls matters: before system, after
				 // thread
				 this.date = new Date();

				 this.startTimeNanos = System.nanoTime();

				 this.threadsMap.clear();

				 final long[] threadIds = this.threadMXBean.getAllThreadIds();

				 for (final long threadId : threadIds) {
						// Exclude jeeObserver tasks
						// String threadName =
						// threadMXBean.getThreadInfo(threadId).getThreadName();
						// if (!MasterTimerTask.TASK_NAME.equals(threadName)) {
						final long currentCpuTimeNanos = this.threadMXBean.getThreadCpuTime(threadId);
						this.threadsMap.put(new Long(threadId), new Long(currentCpuTimeNanos));
						// }
				 }

				 // Memory
				 this.heapMemoryUsedBytes = JvmObserver.memoryMXBean.getHeapMemoryUsage().getUsed();
				 this.heapMemoryCommittedBytes = JvmObserver.memoryMXBean.getHeapMemoryUsage().getCommitted();
				 this.heapMemoryMaxBytes = JvmObserver.memoryMXBean.getHeapMemoryUsage().getMax();

				 this.nonHeapMemoryUsedBytes = JvmObserver.memoryMXBean.getNonHeapMemoryUsage().getUsed();
				 this.nonHeapMemoryCommittedBytes = JvmObserver.memoryMXBean.getNonHeapMemoryUsage().getCommitted();
				 this.nonHeapMemoryMaxBytes = JvmObserver.memoryMXBean.getNonHeapMemoryUsage().getMax();

				 //Threads count
				 this.threads = this.threadMXBean.getThreadCount();

				 // Class loading
				 this.loadedClasses = JvmObserver.classLoadingMXBean.getLoadedClassCount();

				 //Garbage collectors
				 for (GarbageCollectorMXBean garbageCollector : JvmObserver.garbageCollectorMXBeans) {
						this.garbageCollections = this.garbageCollections + garbageCollector.getCollectionCount();
						this.garbageCollectionTimeNanos = this.garbageCollectionTimeNanos + (garbageCollector.getCollectionTime() + TimeUtilities.MILLIS_NANOS);
				 }

				 //Processors count
				 this.processors = Runtime.getRuntime().availableProcessors();

			} else {
				 JeeObserverContext.logger.log(Level.FINE, "JeeObserver context not exists.");
			}
	 }

	 void stop() {

			if (JeeObserverContext.getInstance() != null) {
				 if (this.isRunning()) {
						// The order of these two calls matters: before cpu, after time
						final long[] threadIds = this.threadMXBean.getAllThreadIds();

						this.cpuNanos = 0;

						for (final long threadId : threadIds) {

							 final long currentThreadCpuTimeNanos = this.threadMXBean.getThreadCpuTime(threadId);

							 if (this.threadsMap.containsKey(new Long(threadId))) {
									long difference = currentThreadCpuTimeNanos - this.threadsMap.get(new Long(threadId)).longValue();

									this.cpuNanos = this.cpuNanos + difference;
							 }
						}

						this.stopTimeNanos = System.nanoTime();

						this.timeNanos = this.stopTimeNanos - this.startTimeNanos;

						this.cpuUsagePercentage = (this.cpuNanos / (this.timeNanos * this.processors));

						JeeObserverContext.getInstance().addObserver(this);

				 }
			} else {
				 JeeObserverContext.logger.log(Level.FINE, "JeeObserver context not exists.");
			}

			this.setRunning(false);
	 }

	 public Date getDate() {
			return date;
	 }

	 public long getTimeNanos() {
			return timeNanos;
	 }

	 public long getCpuNanos() {
			return cpuNanos;
	 }

	 public double getCpuUsagePercentage() {
			return cpuUsagePercentage;
	 }

	 public int getProcessors() {
			return processors;
	 }

	 public int getThreads() {
			return threads;
	 }

	 public long getHeapMemoryUsedBytes() {
			return heapMemoryUsedBytes;
	 }

	 public long getHeapMemoryCommittedBytes() {
			return heapMemoryCommittedBytes;
	 }

	 public long getHeapMemoryMaxBytes() {
			return heapMemoryMaxBytes;
	 }

	 public long getNonHeapMemoryUsedBytes() {
			return nonHeapMemoryUsedBytes;
	 }

	 public long getNonHeapMemoryCommittedBytes() {
			return nonHeapMemoryCommittedBytes;
	 }

	 public long getNonHeapMemoryMaxBytes() {
			return nonHeapMemoryMaxBytes;
	 }

	 public long getLoadedClasses() {
			return loadedClasses;
	 }

	 public long getGarbageCollections() {
			return garbageCollections;
	 }

	 public long getGarbageCollectionTimeNanos() {
			return garbageCollectionTimeNanos;
	 }
}
